#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/delay.h>
//#include <errno.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include "../include/ioctl.h"


#define GPIO_PHY_BASE 	0x56000000

#define DRIVER_NAME	"qq2440c2"
#define C2_MAJOR	100
#define C2D_INPUT	0	
#define C2D_OUTPUT	1

void * gpio_vrt_base = NULL;
unsigned int gph_con_vrt = 0;
unsigned int gph_dat_vrt = 0;
unsigned int gph_up_vrt = 0;

inline void set_c2ck_hi(void)
{
	outl(inl(gph_dat_vrt) | (0x1<<10), gph_dat_vrt);  
}

inline void set_c2ck_low(void)
{
	outl(inl(gph_dat_vrt) & ~(0x1<<10), gph_dat_vrt);  
}

/*
 * dir: 1-output 0-input
 */
inline void set_c2d_dir(int dir)
{
	if(dir)
	{
		outl(inl(gph_con_vrt) | (1<<18), gph_con_vrt);
	}
	else
	{
		outl(inl(gph_con_vrt) & ~(0x03<<18), gph_con_vrt);
	}
}

inline void set_c2d_hi(void)
{
	outl(inl(gph_dat_vrt) | (0x1<<9), gph_dat_vrt);  
}

inline void set_c2d_low(void)
{
	outl(inl(gph_dat_vrt) & ~(0x1<<9), gph_dat_vrt);  
}

inline unsigned int get_c2d(void)
{
	return (inl(gph_dat_vrt) & (0x1<<9))?1:0;
}

inline int wait_ready(void)
{
	int wait = 20;
	while(wait--)
	{
		set_c2ck_low();udelay(1);
		set_c2ck_hi();udelay(1);
		if(get_c2d()) return 0; //OK
	}

	printk("C2D not ready\n");
	return 1;
}

unsigned char c2_read_data(void)
{
	int i;
	unsigned long flag;
	unsigned char data = 0;
	local_irq_save(flag);
	set_c2d_dir(C2D_OUTPUT);
	set_c2d_hi();

	set_c2ck_low(); udelay(1);set_c2ck_hi();udelay(1);//start

	set_c2d_low();set_c2ck_low();udelay(1);set_c2ck_hi();udelay(1); //data read
	set_c2d_low();set_c2ck_low();udelay(1);set_c2ck_hi();udelay(1);

	set_c2d_low();set_c2ck_low();udelay(1);set_c2ck_hi();udelay(1); //length 1 byte
	set_c2d_low();set_c2ck_low();udelay(1);set_c2ck_hi();udelay(1);
	
	set_c2d_dir(C2D_INPUT);
	
	wait_ready();

	for(i=0;i<8;i++)
	{
		set_c2ck_low();udelay(1);
		set_c2ck_hi();udelay(1);
		data = data | (get_c2d()<<i);
	}

	set_c2ck_low();udelay(1);set_c2ck_hi();udelay(1); //stop

	local_irq_restore(flag);
	return data;
}

unsigned char c2_read_address(void)
{
	int i;
	unsigned long flag;
	unsigned char data = 0;
	local_irq_save(flag);
	set_c2d_dir(C2D_OUTPUT);
	set_c2d_hi();

	set_c2ck_low(); udelay(1);set_c2ck_hi();udelay(1);//start

	set_c2d_low();set_c2ck_low();udelay(1);set_c2ck_hi();udelay(1); //address read
	set_c2d_hi();set_c2ck_low();udelay(1);set_c2ck_hi();udelay(1);

	
	set_c2d_dir(C2D_INPUT);
	for(i=0;i<8;i++)
	{
		set_c2ck_low();udelay(1);
		set_c2ck_hi();udelay(1);
		data = data | (get_c2d()<<i);
	}

	set_c2ck_low();udelay(1);set_c2ck_hi();udelay(1); //stop

	local_irq_restore(flag);
	return data;
}
void c2_write_address(unsigned char address)
{
	int i;
	unsigned long flag;
	local_irq_save(flag);
	set_c2d_dir(C2D_OUTPUT);
	set_c2d_hi();

	set_c2ck_low(); udelay(1);set_c2ck_hi();udelay(1);//start

	set_c2d_hi();set_c2ck_low();udelay(1);set_c2ck_hi();udelay(1); //address write
	set_c2d_hi();set_c2ck_low();udelay(1);set_c2ck_hi();udelay(1);

	for(i=0;i<8;i++)
	{
		set_c2ck_low();
		(address & 0x01)?set_c2d_hi():set_c2d_low(); 
		udelay(1);
		set_c2ck_hi();udelay(1);
		address = address>>1;
	}

	set_c2d_dir(C2D_INPUT);
	for(i=0;i<8;i++)
	set_c2ck_low();udelay(1);set_c2ck_hi();udelay(1); //stop

	local_irq_restore(flag);
}

int c2_write_data(unsigned char data)
{
	int i, rs;
	unsigned long flag;
	local_irq_save(flag);
	set_c2d_dir(C2D_OUTPUT);
	set_c2d_hi();

	set_c2ck_low(); udelay(1);set_c2ck_hi();udelay(1);//start

	set_c2d_hi();set_c2ck_low();udelay(1);set_c2ck_hi();udelay(1); //data write
	set_c2d_low();set_c2ck_low();udelay(1);set_c2ck_hi();udelay(1);

	set_c2d_low();set_c2ck_low();udelay(1);set_c2ck_hi();udelay(1); //length 1 byte
	set_c2d_low();set_c2ck_low();udelay(1);set_c2ck_hi();udelay(1);
	
	for(i=0;i<8;i++)
	{
		set_c2ck_low();
		(data & 0x01)?set_c2d_hi():set_c2d_low(); 
		udelay(1);
		set_c2ck_hi();udelay(1);
		data = data>>1;
	}

	rs=0;
	set_c2d_dir(C2D_INPUT);
	if(wait_ready())
	{
		rs = -EFAULT;
	}
	set_c2ck_low();udelay(1);set_c2ck_hi();udelay(1); //stop

	local_irq_restore(flag);
	return rs;
}

int qq2440c2_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
{
	switch(cmd)
	{
	case C2_WRITE_ADDRESS:
		c2_write_address((unsigned char)arg);
		return 0;
	case C2_READ_ADDRESS:
		return put_user(c2_read_address(), (unsigned char*)arg);
	case C2_WRITE_DATA:
		return c2_write_data((unsigned char)arg);
	case C2_READ_DATA:
		return put_user(c2_read_data(), (unsigned char*)arg);

	case C2_RESET:
		set_c2ck_low();
		msleep(100);
		set_c2ck_hi();
		return 0;
	}
	return 0;
}

struct file_operations qq2440c2_fops = 
{
	ioctl:qq2440c2_ioctl
};


static int __init qq2440c2_init(void)
{
	int ret;
	printk("qq2440c2 loaded.\n");
	gpio_vrt_base = ioremap(GPIO_PHY_BASE, 0x100);
	if(NULL == gpio_vrt_base)
	{
		printk("ioremap failed.\n");
		return -ENOMEM;
	}

	printk("GPIO_PHY_BASE mapped to: %08X\n", (unsigned int)gpio_vrt_base);
	gph_con_vrt = (unsigned int)gpio_vrt_base + 0x70;
	gph_dat_vrt = (unsigned int)gpio_vrt_base + 0x74;
	gph_up_vrt  = (unsigned int)gpio_vrt_base + 0x78;

	outl(inl(gph_con_vrt) & ~(0xFL<<18), gph_con_vrt);
	outl(inl(gph_con_vrt) | 0x5L<<18, gph_con_vrt);  //gph9, gph10 output mode.
	outl(inl(gph_up_vrt) & ~(0x3L<<9), gph_up_vrt);  //gph9, gph10 pullup enabled.
		
	ret = register_chrdev(C2_MAJOR, DRIVER_NAME, &qq2440c2_fops);


	return ret;
}

static void __exit qq2440c2_exit(void)
{
	if(NULL!=gpio_vrt_base)
	{
		iounmap(gpio_vrt_base);
	}
	unregister_chrdev(C2_MAJOR, DRIVER_NAME);
	printk("qq2440c2 unload.\n");

}


module_init(qq2440c2_init);
module_exit(qq2440c2_exit);
